#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fstream>
#include <atomic>
#include <boost/algorithm/string.hpp>
#include <jsoncpp/json/json.h>
#include <sys/time.h>

#include "./Log.hpp"


namespace ns_util
{
    class PathUtil
    {
    public:
        static const std::string TEMP_PATH;

        // test -> ./temp/test.cc
        static std::string BuildFullFile(const std::string &file_name, const std::string &suffix)
        {
            std::string ret = TEMP_PATH;
            ret += file_name;
            ret += suffix;

            return ret;
        }

        // 构建源文件加路径名
        static std::string Src(const std::string &file_name)
        {
            return BuildFullFile(file_name, ".cpp");
        }

        static std::string Exe(const std::string &file_name)
        {
            return BuildFullFile(file_name, ".exe");
        }

        static std::string Compile_error(const std::string &file_name)
        {
            return BuildFullFile(file_name, ".compile_error");
        }

        static std::string Stdin(const std::string &file_name)
        {
            return BuildFullFile(file_name, ".stdin");
        }

        static std::string Stdout(const std::string &file_name)
        {
            return BuildFullFile(file_name, ".stdout");
        }

        static std::string Stderr(const std::string &file_name)
        {
            return BuildFullFile(file_name, ".stderr");
        }
    };

    class TimeUtil
    {
    public:
        static std::string GetTimeStamp()
        {
            struct timeval tv;
            gettimeofday(&tv, nullptr);

            return std::to_string(tv.tv_sec);
        }

        static std::string GetTimeMs()
        {
            struct timeval tv;
            gettimeofday(&tv, nullptr);

            long long MilliSec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
            return std::to_string(MilliSec);
        }
    };

    const std::string PathUtil::TEMP_PATH = "./temp/";

    class FileUtil
    {
    public:
        static bool IsFileExists(const std::string& path_name)
        {
            struct stat* statbuf = new struct stat;
            
            return  stat(path_name.c_str(), statbuf) == 0;
        }

        static std::string UniqFileName()
        {
            static std::atomic_int id(0);
            ++id;
            std::string Ms = TimeUtil::GetTimeMs();

            return Ms + "_" + std::to_string(id);
        }

        static bool WriteFile(const std::string& path_name, const std::string& content)
        {
            std::ofstream out(path_name, std::ofstream::out);
            if(!out.is_open())
            {
                return false;
            }
            out << content;

            return true;
        }

        // keep 为 true 表示结尾加 \n, 否则为不加 \n
        static bool ReadFile(const std::string& path_name, std::string* content, bool keep = false)
        {
            (*content).clear();
            std::ifstream in(path_name, std::ifstream::in);
            if(!in.is_open())
                return false;

            std::string line;
            while(std::getline(in, line))
            {
                (*content) += line;
                (*content) += (keep ? "\n" : "");
            }
            
            return true;
        }

        static bool ReadFileBinary(const std::string& path_name, std::string* content)
        {
            (*content).clear();
            std::ifstream in(path_name, std::ifstream::binary);
            if(!in.is_open())
                return false;

            in.seekg(0, in.end);
            int length = in.tellg();
            in.seekg(0, in.beg);

            (*content).resize(length);
            
            in.read((char*)(*content).c_str(), length);
            return true;
        }
    };

    class StringUtil
    {
    public:
        static void SplitString(const std::string& str, const std::string& sep, std::vector<std::string>* ret)
        {
            // 手动方法
            int begin = 0;
            size_t pos = str.find(sep, begin);
            while(pos != std::string::npos)
            {
                std::string sub_str = str.substr(begin, pos - begin);
                if(sub_str.size() > 0)
                    (*ret).push_back(sub_str);
                begin = pos + 1;
                pos = str.find(sep, begin);
            }

            pos = str.size();
            std::string sub_str = str.substr(begin, pos - begin);
            if(sub_str.size() > 0)
                    (*ret).push_back(sub_str);

            // Boost 库
            // boost::split(*ret, str, boost::is_any_of(sep), boost::algorithm::token_compress_on);
        }
    };

    class json_util
    {
    public:
        static bool serialize(Json::Value& root, std::string& str)
        {
            Json::StyledWriter writer;
            str = writer.write(root);

            return true;
        }

        static bool deserialize(Json::Value& root, std::string& str)
        {
            Json::Reader reader;
            bool ret = reader.parse(str, root);
            if(ret == false)
            {
                LOG(ERROR, "deserialize fail");
                return false;
            }

            return true;
        }
    };

/////////////-------------------------------------------------------------------------------
    class Mysql
    {
    public:
        Mysql(const std::string host, const std::string user, const std::string passwd, const std::string db, uint16_t port)
            : _host(host), _user(user), _passwd(passwd), _db(db), _port(port)
        {
        }
        void MysqlInit()
        {
            _my = mysql_init(nullptr);
            if (!_my)
            {
                LOG(FATAL, "mysql_init fail, errmsg: %s\n", strerror(errno));
                return;
            }

            if (!mysql_real_connect(_my, _host.c_str(), _user.c_str(), _passwd.c_str(), _db.c_str(), _port, nullptr, 0))
            {
                LOG(FATAL, "mysql_real_connect fail, errmsg: %s\n", strerror(errno));
                return;
            }

            int n = mysql_set_character_set(_my, "utf8");
            if (n != 0)
            {
                LOG(FATAL, "mysql_set_character_set fail, errmsg: %s\n", strerror(errno));
                return;
            }
        }

        bool MysqlQuery(std::string query)
        {
            if (mysql_query(_my, query.c_str()))
            {
                LOG(FATAL, "mysql_query: [%s] fail\n", query.c_str());
                return false;
            }
            return true;
        }

        void ShowRes()
        {
            MYSQL_RES *res = mysql_store_result(_my);
            int row = mysql_num_rows(res);
            int col = mysql_num_fields(res);

            MYSQL_FIELD *field = mysql_fetch_fields(res);
            for (int i = 0; i < col; ++i)
            {
                std::cout << field[i].name << "\t";
            }
            std::cout << std::endl;

            for (int i = 0; i < row; ++i)
            {
                MYSQL_ROW line = mysql_fetch_row(res);
                for (int j = 0; j < col; j++)
                {
                    std::cout << line[j] << "\t";
                }
                std::cout << std::endl;
            }

            mysql_free_result(res);
        }

        void MysqlClose()
        {
            mysql_close(_my);
        }

        MYSQL *GetMy()
        {
            return _my;
        }

    private:
        MYSQL *_my;
        const std::string _host;
        const std::string _user;
        const std::string _passwd;
        const std::string _db;
        uint16_t _port;
    };

    class UserCtl
    {
    private:
        static const int sha2_num = 256;

        // std::string mysql_host = "101.34.254.139";
        // std::string mysql_user = "lw";
        // std::string mysql_passwd = "Liwei200408#";
        // std::string mysql_db = "oj";
        // uint16_t Mysql_port = 3306;

    public:
        UserCtl(const std::string host, const std::string user, const std::string passwd, const std::string db, 
        uint16_t port = 3306)
            : _my(host, user, passwd, db, port)
        {
            _my.MysqlInit();
        }
        ~UserCtl()
        {
            _my.MysqlClose();
        }

        // 新增用户
        // "张三" "123456"
        bool InsertUser(Json::Value &root)
        {
    #define INSERT_USER "insert into oj_user values(null, '%s', sha2('%s', %d))"

            std::string username = root["username"].asString();
            std::string password = root["password"].asString();

            if (SelectUserByName(root, username))
            {
                LOG(ERROR, "this username not unique\n");
                return false;
            }

            char buffer[1024];
            snprintf(buffer, sizeof(buffer), INSERT_USER, username.c_str(), password.c_str(), sha2_num);

            if (!_my.MysqlQuery(buffer))
            {
                LOG(ERROR, "insert user fail, sql: %s\n", buffer);
                return false;
            }
            return true;
        }

        // 登录验证，并返回详细信息
        // "张三" "123456"
        bool Login(Json::Value &root)
        {
    #define SELECT_USER "select id, username, password from oj_user where username='%s' and password=sha2('%s', %d)"

            std::string username = root["username"].asString();
            std::string password = root["password"].asString();
            char buffer[1024];
            snprintf(buffer, sizeof(buffer), SELECT_USER, username.c_str(), password.c_str(), sha2_num);
            MYSQL_RES *res = nullptr;
            {
                std::unique_lock<std::mutex> lock(_mtx);
                if (!_my.MysqlQuery(buffer))
                {
                    LOG(ERROR, "Login fail, sql: %s\n", buffer);
                    return false;
                }

                res = mysql_store_result(_my.GetMy());
                if (res == nullptr)
                {
                    LOG(ERROR, "Login(): NOT find user %s\n", username.c_str());
                    return false;
                }
            }

            return true;
        }

    //     bool SelectUserById(Json::Value &root, int id)
    //     {
    // #define SELECT_USERBYID "select id, username, password from oj_user where id=%d"

    //         char buffer[1024];
    //         snprintf(buffer, sizeof(buffer), SELECT_USERBYID, id);
    //         MYSQL_RES *res = nullptr;
    //         {
    //             std::unique_lock<std::mutex> lock(_mtx);
    //             if (!_my.MysqlQuery(buffer))
    //             {
    //                 LOG(ERROR, "SelectUserById fail, sql: %s\n", buffer);
    //                 return false;
    //             }

    //             res = mysql_store_result(_my.GetMy());
    //             if (res == nullptr)
    //             {
    //                 LOG(ERROR, "SelectUserById(): NOT find user \n");
    //                 return false;
    //             }
    //         }
            
    //         int col_num = mysql_num_fields(res);
    //         if (col_num != 5)
    //         {
    //             LOG(ERROR, "Login return %d fields\n", col_num);
    //             return false;
    //         }
    //         MYSQL_ROW row = mysql_fetch_row(res);
    //         if(row == nullptr)
    //         {
    //             LOG(ERROR, "id: %d, mysql_fetch_row fields\n", id);
    //             return false;
    //         }

    //         root["username"] = row[0];
    //         root["id"] = id;
    //         root["password"] = row[1];
    //         root["score"] = std::stoi(row[2]);
    //         root["total_count"] = std::stoi(row[3]);
    //         root["win_count"] = std::stoi(row[4]);

    //         mysql_free_result(res);
    //         return true;
    //     }

        bool SelectUserByName(Json::Value &root, std::string username)
        {
    #define SELECT_USERBYNAME "select id, password from oj_user where username='%s'"
            char buffer[1024];
            snprintf(buffer, sizeof(buffer), SELECT_USERBYNAME, username.c_str());
            MYSQL_RES *res = nullptr;
            {
                std::unique_lock<std::mutex> lock(_mtx);
                if (!_my.MysqlQuery(buffer))
                {
                    LOG(ERROR, "SelectUserByName fail, sql: %s\n", buffer);
                    return false;
                }

                res = mysql_store_result(_my.GetMy());
                if (res == nullptr)
                {
                    LOG(ERROR, "SelectUserByName(): NOT find user %s\n", username.c_str());
                    return false;
                }
            }
            
            int col_num = mysql_num_fields(res);
            if (col_num != 2)
            {
                LOG(ERROR, "Login return %d fields\n", col_num);
                return false;
            }
            MYSQL_ROW row = mysql_fetch_row(res);
            if(row == nullptr)
            {
                LOG(ERROR, "username: %s, mysql_fetch_row fields\n", username.c_str());
                return false;
            }

            root["username"] = username.c_str();
            root["id"] = std::stoi(row[0]);
            root["password"] = row[1];

            LOG(INFO, "username: %s, uid: %d\n", username.c_str(), root["id"].asInt());

            mysql_free_result(res);
            return true;
        }
    private:
        Mysql _my;
        std::mutex _mtx;
    };
}